﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.Linq;

namespace CCodeTreeLib
{
    public enum Modifier { Public, Private, Internal, Protected, InternalProtected }

    #region CCodeStatement

    public abstract class CCodeStatement : IEnumerable<CCodeStatement>
    {
        private readonly StringBuilder _codeBuilder = new StringBuilder();
        public bool Close { get; set; }
        public bool AsLocalReference { get; set; }
        protected int _i;

        private List<CCodeStatement> _statements = new List<CCodeStatement>();
        private List<CCodeStatement> Statements
        {
            get { return _statements; }
            set { _statements = value; }
        }

        public CCodeStatement this[int i]
        {
            get { return Statements[i]; }
        }

        public int Count
        {
            get { return _statements.Count; }
        }

        public void AddRange(params CCodeStatement[] statments)
        {
            _statements.AddRange(statments);
        }

        public void AddStatement(string code)
        {
            AddStatement(new CCodeSnippet(code));
        }

        public T AddStatement<T>(T st) where T : CCodeStatement
        {
            if (this is CCodeNamespace && st is CCodeClass)
            {
                if (this.Statements.OfType<CCodeClass>().Any(x=>x.Name == ((st as CCodeClass).Name)))
                    throw new Exception("Class already exists");
            }
            if (this is CCodeClass && st is CCodeProperty)
            {
                if (this.Statements.OfType<CCodeClass>().Any(x => x.Name == ((st as CCodeProperty).Name)))
                    throw new Exception("Class already exists");
            }
            if (this is CCodeInterface && st is CCodeClass)
            {
                if (this.Statements.OfType<CCodeInterface>().Any(x => x.Name == ((st as CCodeClass).Name)))
                    throw new Exception("Class already exists");
            }
            _statements.Add(st);
            return st;
        }

        public string ToStringProp { get { return ToString(); } }

        protected void WriteLine()
        {
            WriteLine(string.Empty);
        }

        protected void WriteLine(string code)
        {
            foreach (string s in code.Split(new [] {"\r\n"}, StringSplitOptions.None))
                _codeBuilder.AppendLine(Indent(s));
        }

        protected void Write(string code)
        {
            _codeBuilder.Append(Indent(code));
        }

        protected void BeginBlock()
        {
            WriteLine("{");
            _i++;
        }

        protected void EndBlock()
        {
            _i--;
            Write("}");
        }

        protected void Clear()
        {
            _i = 0;
            _codeBuilder.Clear();
        }

        protected string GetString()
        {
            return _codeBuilder.ToString();
        }

        private string Indent(string str)
        {
            return new string('\t', _i) + str;
        }

        IEnumerator<CCodeStatement> IEnumerable<CCodeStatement>.GetEnumerator()
        {
            return _statements.GetEnumerator();
        }
        IEnumerator IEnumerable.GetEnumerator()
        {
            return _statements.GetEnumerator();
        }
    }

    #endregion

    public class CCodeTree: CCodeStatement
    {
        public CCodeTree()
        {
            Usings = new List<CCodeUsing>();
            Comments = new List<string>();
        }
        public List<CCodeUsing> Usings { get; set; }

        public List<string> Comments { get; set; }

        public override string ToString()
        {
            Clear();
            foreach (var c in Comments)
                WriteLine("//" + c);
            foreach (var u in Usings)
                WriteLine(u.ToString());
            foreach (var s in this)
                WriteLine(s.ToString());
            return GetString();
        }
    }

    public class CCodeNamespace : CCodeStatement
    {
        private string _name;

        public CCodeNamespace()
        {

        }

        public CCodeNamespace(string name)
        {
            _name = name;
        }

        public string Name
        {
            get { return _name; }
            set { _name = value; }
        }

        public override string ToString()
        {
            Clear();
            WriteLine("namespace " + Name);
            BeginBlock();
            foreach (var s in this)
                WriteLine(s.ToString());
            EndBlock();
            return GetString();
        }
        public CCodeClass AddClass(CCodeClass cls)
        {
            AddStatement(cls);
            return cls;
        }
        public CCodeInterface AddInterface(CCodeInterface i)
        {
            AddStatement(i);
            return i;
        }
    }

    public class CCodeUsing : CCodeStatement
    {
        public CCodeUsing(string name)
        {
            AddStatement(new CCodeSnippet(name));
        }
        public override string ToString()
        {
            Clear();
            Write("using {0};".F(this[0].ToString()));
            return GetString();
        }
    }

    public class CCodeEnum: CCodeStatement
    {
        public CCodeEnum(string name, params CCodeAssign[] values)
        {
            Values = new List<CCodeAssign>();
            Name = name;
        }
        public string Name { get; set; }
        public List<CCodeAssign> Values { get; set; }

        public override string ToString()
        {
            Clear();
            WriteLine("public enum " + Name);
            BeginBlock();
            foreach (var v in Values)
            {
                Write(v.ToString());
                if (Array.IndexOf(Values.ToArray(),v) != Values.Count -1)
                {
                    WriteLine(", ");
                }
            }
            WriteLine();
            EndBlock();
            return GetString();
        }
    }

    public class CCodeEventDeclaration : CCodeStatement
    {
        string _type;
        string _name;
        public CCodeEventDeclaration(string type, string name)
        {
            _type = type;
            _name = name;
        }
        public override string ToString()
        {
            return "public event {0} {1};".F(_type, _name);
        }
    }

    #region CCodeType

    public abstract class CCodeMembersContainer : CCodeStatement
    {
        public void WriteMembers(bool forInterface = false)
        {
            //Clear();
            var events = this.OfType<CCodeEventDeclaration>();
            var fields = this.OfType<CCodeField>();
            var constructors = this.OfType<CCodeConstructor>();
            var regions = this.OfType<CCodeRegion>();
            var props = this.OfType<CCodeProperty>();
            var methods = this.OfType<CCodeMethod>();
            foreach (var e in events)
            {
                WriteLine(e.ToString());
            }
            foreach (var f in fields)
            {
                f.Close = true;
                WriteLine(f.ToString());
            }
            foreach (var c in constructors)
            {
                WriteLine(c.ToString());
            }
            foreach (var p in props)
            {
                p.ForInterface = forInterface;
                WriteLine(p.ToString());
            }
            foreach (var m in methods)
            {
                m.WithModifier = !forInterface;
                m.ForInterface = forInterface;
                WriteLine(m.ToString());
            }
            foreach (var r in regions)
            {
                WriteLine(r.ToString());
            }
        }
    }

    public abstract class CCodeType: CCodeMembersContainer
    {
        protected CCodeType()
        {
            Attributes = new List<CCodeAttribute>();
        }

        public T AddMember<T>(T member) where T: CCodeStatement
        {
            AddStatement(member);
            if (member is CCodeAttribute)
                Attributes.Add(member as CCodeAttribute);
            return member;
        }
        public List<CCodeAttribute> Attributes { get; set; }
    }

    public class CCodeClass : CCodeType
    {
        public CCodeClass()
        {
            Modifier = Modifier.Internal;
            ImplementedInterfaces = new List<string>();
            IsPartial = true;
            Comments = new List<string>();
        }
        public CCodeClass(string name)
            : this()
        {
            Name = name;
        }
        /// <summary>
        /// Creates a Getter and Setter for a property that encapsulates a field.
        /// </summary>
        /// <param name="type"></param>
        /// <param name="name"></param>
        /// <param name="encapsulate"></param>
        /// <param name="initializer"></param>
        /// <returns></returns>
        public CCodeProperty CreateProperty(string type, string name, bool encapsulate = false, CCodeStatement initializer = null)
        {
            var cprop = new CCodeProperty(name, type);
            if (initializer!=null || encapsulate)
            {
                cprop.Field = new CCodeField(type, "_" + name.ToLower()) { Initializer = initializer };
                AddMember(cprop.Field);
            }
            AddMember(cprop);
            return cprop;

        }

        public Modifier Modifier { get; set; }
        public string Name { get; set; }
        public bool IsStatic { get; set; }
        public bool IsPartial { get; set; }
        public bool IsAbstract { get; set; }
        public bool IsSealed { get; set; }

        public string BaseClass { get; set; }
        public List<string> ImplementedInterfaces { get; set; }

        public List<string> Comments { get; set; }

        public override string ToString()
        {
            Clear();
            foreach (var c in Comments)
                WriteLine("//" + c);
            foreach (var a in Attributes)
                WriteLine(a.ToString());
            string isSealed = string.Empty;
            string isAbstract = string.Empty;
            string isPartial = string.Empty;
            if (IsAbstract)
                isAbstract = "abstract ";
            if (IsSealed)
                isSealed = "sealed ";
            if (IsPartial)
                isPartial = "partial ";
            Write(Modifier.ToString().ToLower() + " " + isAbstract + isSealed + isPartial + "class " + Name);
            var list = new List<string>();
            if (!string.IsNullOrEmpty(BaseClass))
                list.Add(BaseClass);
            list.AddRange(ImplementedInterfaces);
            if (list.Count > 0)
                Write(" : ");
            foreach (var s in list)
            {
                Write(s);
                if (Array.IndexOf(list.ToArray(), s) != list.Count - 1)
                    Write(" ,");
            }
            WriteLine();
            BeginBlock();
            WriteMembers();
            EndBlock();
            return GetString();
        }
        
    }

    public class CCodeInterface : CCodeType
    {
        public CCodeInterface(string name)
        {
            Name = name;
            IsPartial = true;
            Modifier = Modifier.Internal;
            Comments = new List<string>();
        }
        public string Name { get; set; }
        public bool IsPartial { get; set; }
        public Modifier Modifier { get; set; }

        public List<string> Comments { get; set; }

        public override string ToString()
        {
            Clear();
            foreach (var c in Comments)
                WriteLine("//" + c);
            foreach (var a in Attributes)
                WriteLine(a.ToString());
            string isPartial = string.Empty;
            string modifier = Modifier.ToString().ToLower() + " ";
            if (IsPartial)
                isPartial = "partial ";
            WriteLine(string.Format("{0}{1}interface {2}", modifier, isPartial, Name));
            BeginBlock();
            WriteMembers(true);
            EndBlock();
            return GetString();
        }
    }

    #endregion

    #region CCodeMember

    public class CCodeMember : CCodeStatement { }

    public class CCodeConstructor : CCodeMember
    {
        public CCodeConstructor(CCodeClass owner)
        {
            Owner = owner;
            Parameters = new List<CCodeField>();
            Modifier = Modifier.Public;
            Comments = new List<string>();
            BaseConstructorParams = new List<CCodeField>();
            ThisConstructorParams = new List<CCodeField>();
        }

        public CCodeConstructor(CCodeClass owner, params CCodeField[] parameters)
            : this(owner)
        {
            Parameters.AddRange(parameters);
        }

        public List<CCodeField> Parameters { get; set; }
        public Modifier Modifier { get; set; }
        public CCodeClass Owner { get; set; }
        public List<CCodeField> BaseConstructorParams { get; set; }
        public List<CCodeField> ThisConstructorParams { get; set; }
        public List<string> Comments { get; set; }

        public override string ToString()
        {
            Clear();
            foreach (var c in Comments)
                WriteLine("//" + c);
            Write(Modifier.ToString().ToLower() + " " + Owner.Name + "(");
            foreach (var p in Parameters)
            {
                Write(p.TypeName + " " + p.Name);
                if (Array.IndexOf(Parameters.ToArray(), p) != Parameters.Count - 1)
                    Write(", ");
            }
            Write(")");
            if (BaseConstructorParams != null && BaseConstructorParams.Count > 0)
            {
                var paramlist = new StringBuilder();
                paramlist.Append(": base(");
                foreach (var s in BaseConstructorParams)
                {
                    paramlist.Append(s.Name);
                    if (Array.IndexOf(BaseConstructorParams.ToArray(), s) != BaseConstructorParams.Count - 1)
                        paramlist.Append(", ");
                }
                paramlist.Append(")");
                Write(paramlist.ToString());
            }
            if (ThisConstructorParams != null && ThisConstructorParams.Count > 0)
            {
                var paramlist = new StringBuilder();
                paramlist.Append(": this(");
                foreach (var s in ThisConstructorParams)
                {
                    paramlist.Append(s.Name);
                    if (Array.IndexOf(ThisConstructorParams.ToArray(), s) != ThisConstructorParams.Count - 1)
                        paramlist.Append(", ");
                }
                paramlist.Append(")");
                Write(paramlist.ToString());
            }
            if (Count > 0)
            {
                WriteLine();
                BeginBlock();
                foreach (var s in this)
                {
                    s.Close = true;
                    WriteLine(s.ToString());
                }
                EndBlock();
            }
            else
                Write(" { }");
            return GetString();
        }
    }

    public class CCodeProperty : CCodeMember 
    {
        public CCodeProperty()
        {
            SetStatements = new List<CCodeStatement>();
            GetStatements = new List<CCodeStatement>();
            IsPartial = true;
            IsVirtual = true;
            Modifier = Modifier.Public;
            Attributes = new List<CCodeAttribute>();
            Comments = new List<string>();
        }

        public CCodeProperty(string name, Type type) : this()
        {
            Name = name;
            Type = type;
        }

        public CCodeProperty(string name, string type)
            : this()
        {
            Name = name;
            TypeName = type;
        }

        public CCodeProperty(string name, CCodeField field) :this()
        {
            Name = name;
            Field = field;
        }

        private Type Type { get; set; }
        private string _typeName;

        public string TypeName
        {
            get 
            {
                if (Type != null)
                    return Type.Name;
                if (Field != null && !string.IsNullOrEmpty(Field.TypeName))
                    return Field.TypeName;
                return _typeName; 
            }
            set 
            { 
                _typeName = value;
            }
        }


        public T AddGetStatement<T>(T statement)
        {
            GetStatements.Add(statement as CCodeStatement);
            return statement;
        }
        public T AddSetStatement<T>(T statement)
        {
            SetStatements.Add(statement as CCodeStatement);
            return statement;
        }
        public void AddGetStatement(string code)
        {
            GetStatements.Add(new CCodeSnippet(code));
        }
        public void AddSetStatement(string code)
        {
            SetStatements.Add(new CCodeSnippet(code));
        }
        public string Name { get; set; }
        public CCodeField Field { get; set; }
        public bool IsPartial { get; set; }
        public bool IsVirtual { get; set; }
        public bool IsOverridden { get; set; }
        public CCodeField Indexer { get; set; }

        public bool ForInterface { get; set; }

        public List<CCodeStatement> SetStatements { get; set; }
        public List<CCodeStatement> GetStatements { get; set; }
        public Modifier Modifier { get; set; }
        public List<CCodeAttribute> Attributes { get; set; }

        public List<string> Comments { get; set; }

        public override string ToString()
        {
            Clear();
            if (AsLocalReference)
            {
                Write(Name);
                return GetString();
            }
            foreach (var c in Comments)
                WriteLine("//" + c);
            if (!ForInterface)
            {
                foreach (var a in Attributes)
                    WriteLine(a.ToString());
            }
            string modifier = string.Empty;
            if (!ForInterface)
                modifier = Modifier.ToString().ToLower() + " ";
            string isoverridden = string.Empty;
            if (IsOverridden && !ForInterface)
                isoverridden = "override ";
            string indexer = (Indexer != null) ? string.Format("[{0} {1}]", Indexer.TypeName, Indexer.Name) : string.Empty;
            if (ForInterface || (Field == null && GetStatements.Count == 0 && SetStatements.Count == 0))
            {
                // auto property
                Write(modifier + isoverridden + TypeName + " " + Name + " { get; set; }");
            }
            else
            {
                WriteLine(modifier + TypeName + " " + Name + indexer);
                BeginBlock();

                bool writtenField = false;
                if (Field != null)
                {
                    WriteLine("get");
                    BeginBlock();
                    WriteLine("return " + Field.Name + ";");
                    foreach (var g in GetStatements)
                        Write(g.ToString());
                    EndBlock();
                    WriteLine();
                    WriteLine("set");
                    BeginBlock();
                    WriteLine(Field.Name + " = value;");
                    foreach (var s in SetStatements)
                        WriteLine(s.ToString());
                    EndBlock();
                    WriteLine();
                    writtenField = true;

                }
                if (GetStatements.Count > 0 && !writtenField)
                {
                    WriteLine("get");
                    BeginBlock();
                    foreach (var g in GetStatements)
                        WriteLine(g.ToString());
                    EndBlock();
                    WriteLine();
                }
                if (SetStatements.Count > 0 && !writtenField)
                {
                    WriteLine("set");
                    BeginBlock();
                    foreach (var s in SetStatements)
                        WriteLine(s.ToString());
                    EndBlock();
                    WriteLine();
                }
                EndBlock();
            }
            return GetString();
        }
    }

    public class CCodeField : CCodeMember
    {
        public bool Declared { get; set; }
        public CCodeField()
        {
            Modifier = Modifier.Private;
            Comments = new List<string>();
        }

        public CCodeField(Type type, string name)
            : this()
        {
            Name = name;
            Type = type;
        }

        public CCodeField(string type, string name)
            : this()
        {
            Name = name;
            TypeName = type;
        }

        private Type Type { get; set; }
        private string _typeName;

        public string TypeName
        {
            get
            {
                if (Type != null)
                    return Type.Name;
                return _typeName;
            }
            set
            {
                _typeName = value;
            }
        }
        public override string ToString()
        {
            Clear();
            if (AsLocalReference)
            {
                Write(Name);
                return GetString();
            }
            string modifier = string.Empty;
            if (Modifier != Modifier.Private)
                modifier = Modifier.ToString().ToLower() + " ";
            Write(modifier + (IsReadOnly ? "readonly " : "") + TypeName + " " + Name);
            if (Initializer != null)
                Write(" = " + Initializer);
            if (Close)
                Write(";");
            return GetString();
        }

        public string Name { get; set; }
        public bool IsReadOnly { get; set; }
        public CCodeStatement Initializer { get; set; }
        public Modifier Modifier { get; set; }

        public List<string> Comments { get; set; }
    }

    public class CCodeMethod : CCodeMember
    {
        public CCodeMethod()
        {
            Parameters = new List<CCodeField>();
            Modifier = Modifier.Public;
            //Statements = new List<CCodeStatement>();
            Attributes = new List<CCodeAttribute>();
            Comments = new List<string>();
        }

        public CCodeMethod(Type returnType, string name)
            : this()
        {
            Type = returnType;
            Name = name;
        }

        public CCodeMethod(string returnType, string name)
            : this()
        {
            TypeName = returnType;
            Name = name;
        }

        public CCodeMethod(Type returnType, string name, params CCodeField[] parameters)
            : this()
        {
            Type = returnType;
            Name = name;
            Parameters.AddRange(parameters);
        }

        public CCodeMethod(string returnType, string name, params CCodeField[] parameters)
            : this()
        {
            TypeName = returnType;
            Name = name;
            Parameters.AddRange(parameters);

        }

        public override string ToString()
        {
            Clear();
            foreach (var c in Comments)
                WriteLine("//" + c);
            foreach (var a in Attributes)
                WriteLine(a.ToString());
                //WriteAttribute(a);

            string modifier = string.Empty;
            string isvirtual = string.Empty;
            string isoverridden = string.Empty;
            string isabstract = string.Empty;
            string ispartial = string.Empty;
            if (WithModifier)
                modifier = Modifier.ToString().ToLower() + " ";
            if (IsVirtual && !ForInterface)
                isvirtual = "virtual ";
            if (IsOverridden && !ForInterface)
                isoverridden = "override ";
            if (IsAbstract && !ForInterface)
                isabstract = "abstract ";
            if (IsPartial && !ForInterface)
            {
                ispartial = "partial ";
                modifier = "";
            }
            Write(modifier + isvirtual + isabstract + ispartial + isoverridden + TypeName + " " + Name + "(");
            foreach (var p in Parameters)
            {
                Write(p.TypeName + " " + p.Name);
                if (p.Initializer!=null)
                    Write(" = " + p.Initializer);
                if (Array.IndexOf(Parameters.ToArray(), p) != Parameters.Count - 1)
                    Write(", ");
            }
            Write(")");
            if (!ForInterface && !IsAbstract && !IsPartial)
            {
                if (Count > 0)
                {
                    WriteLine();
                    BeginBlock();
                    foreach (var s in this)
                    {
                        s.Close = true;
                        if (s is CCodeField)
                            s.AsLocalReference = true; // as this is a Field, its already declared at Class level.
                        WriteLine(s.ToString());
                    }
                    EndBlock();
                }
                else
                    Write(" { }");
            }
            else
                Write(";");
            return GetString();
        }
        private Type Type { get; set; }
        private string _typeName;

        public string TypeName
        {
            get
            {
                if (Type != null)
                    return Type.Name;
                return _typeName;
            }
            set
            {
                _typeName = value;
            }
        }
        public string Name { get; set; }
        public List<CCodeField> Parameters { get; set; }
        public bool IsVirtual { get; set; }
        public bool IsPartial { get; set; }
        public bool IsOverridden { get; set; }
        public Modifier Modifier { get; set; }
        public List<CCodeAttribute> Attributes { get; set; }

        public bool WithModifier { get; set; }
        public bool ForInterface { get; set; }

        public object Tag { get; set; }

        public List<string> Comments { get; set; }

        public bool IsAbstract { get; set; }
    }

    public class CCodeRegion : CCodeMembersContainer
    {
        readonly string _label;

        public CCodeRegion(string label)
        {
            _label = label;
        }
        public override string ToString()
        {
            Clear();
            WriteLine();
            WriteLine("#region " + _label);
            WriteLine();
            WriteMembers();
            WriteLine();
            Write("#endregion");
            return GetString();
        }
    }

    #endregion

    #region Block statements

    public class CCodeLambda : CCodeStatement
    {
        public List<CCodeField> Parameters { get; set; }
        public CCodeLambda(params CCodeField[] parameters)
        {
            Parameters = new List<CCodeField>(parameters);
        }
        public override string ToString()
        {
            Clear();
            if (Parameters.Count == 0)
                Write("(");
            foreach (var p in Parameters)
            {
                p.AsLocalReference = true;
                Write(p.ToString());
                if (Array.IndexOf(Parameters.ToArray(), p) != Parameters.Count - 1)
                    Write(",");
            }
            if (Parameters.Count == 0)
                Write(")");
            Write(" => ");
            

            if (Count == 1 && this[0].Count == 0)
            {
                this[0].Close = false;
                if (this[0] is CCodeField)
                    this[0].AsLocalReference = true; // as this is a Field, its already declared at Class level.
                Write(this[0].ToString());
            }
            else
            {
                WriteLine();
                BeginBlock();
                foreach (var s in this)
                {
                    s.Close = true;
                    if (s is CCodeField)
                        s.AsLocalReference = true; // as this is a Field, its already declared at Class level.
                    WriteLine(s.ToString());
                }
                EndBlock();
            }

            if (Close)
                Write(";");
            return GetString();
        }
    }

    public class CCodeLockStatment : CCodeStatement
    {
        public CCodeLockStatment(string obj)
        {
            Object = obj;
        }

        public string Object { get; set; }

        public override string ToString()
        {
            Clear();
            WriteLine("lock(" + Object + ")");
            BeginBlock();
            foreach (var s in this)
                WriteLine(s.ToString());
            EndBlock();
            return GetString();
        }
    }

    public class CCodeIfStatement : CCodeStatement
    {
        public CCodeIfStatement(string condition)
        {
            Condition = condition;
            ElseStatements = new List<CCodeStatement>();
            ElseIfStatements = new List<CCodeElseIfStatement>();
        }

        public CCodeIfStatement(string condition, params CCodeStatement[] trueStatements)
            : this(condition)
        {
            AddRange(trueStatements);
        }
        public override string ToString()
        {
            Clear();
            WriteLine("if (" + Condition + ")");
            if (Count == 1 && this[0].Count ==0)
            {
                _i++;
                Write(this[0].ToString());
                //WriteStatement(s[0]);
                _i--;
            }
            else
            {
                BeginBlock();
                foreach (var ts in this)
                    WriteLine(ts.ToString());
                EndBlock();
            }
            foreach (var eis in ElseIfStatements)
            {
                if (eis.Count > 0 && eis[0].Count == 0)
                {
                    WriteLine();
                    WriteLine("else if (" + eis.Condition + ")");
                    if (eis.Count == 1)
                    {
                        _i++;
                        Write(eis[0].ToString());
                        _i--;
                    }
                    else
                    {
                        BeginBlock();
                        foreach (var eiss in eis)
                        {
                            WriteLine(eiss.ToString());
                        }
                        EndBlock();
                    }
                }
            }
            if (ElseStatements.Count > 0)
            {
                WriteLine();
                WriteLine("else");
                if (ElseStatements.Count == 1 && ElseStatements[0].Count ==0)
                {
                    _i++;
                    Write(ElseStatements[0].ToString());
                    _i--;
                }
                else
                {
                    BeginBlock();
                    foreach (var es in ElseStatements)
                    {
                        WriteLine();
                        Write(es.ToString());
                    }
                    EndBlock();
                }
            }
            return GetString();
        }
        public string Condition { get; set; }
        public List<CCodeStatement> ElseStatements { get; set; }
        public List<CCodeElseIfStatement> ElseIfStatements { get; set; }
    }

    public class CCodeElseIfStatement : CCodeStatement
    {
        public string Condition { get; set; }
        public override string ToString()
        {
            return Condition;
        }
    }

    public class CCodeWhileStatement : CCodeStatement
    {
        public CCodeWhileStatement(string obj)
        {
            Object = obj;
        }

        public CCodeWhileStatement(string obj, params CCodeStatement[] statements)
            : this(obj)
        {
            AddRange(statements);
        }
        public string Object { get; set; }
        public override string ToString()
        {
            Clear();
            WriteLine("while (" + Object + ")");
            if (Count == 1 && this[0].Count == 0)
            {
                _i++;
                foreach (var s in this)
                    WriteLine(s.ToString());
                _i--;
            }
            else
            {
                BeginBlock();
                foreach (var s in this)
                    WriteLine(s.ToString());
                EndBlock();
            }
            return GetString();
        }
    }

    public class CCodeUsingStatement : CCodeStatement
    {
        public CCodeUsingStatement(string obj)
        {
            Object = obj;
        }

        public CCodeUsingStatement(string obj, params CCodeStatement[] statements)
            : this(obj)
        {
            AddRange(statements);
        }
        public string Object { get; set; }
        public override string ToString()
        {
            Clear();
            WriteLine("using (" + Object + ")");
            if (Count == 1 && this[0].Count == 0)
            {
                _i++;
                foreach (var s in this)
                    WriteLine(s.ToString());
                _i--;
            }
            else
            {
                BeginBlock();
                foreach (var s in this)
                    WriteLine(s.ToString());
                EndBlock();
            }
            return GetString();
        }
    }

    public class CCodeForeachStatement : CCodeStatement
    {
        public CCodeForeachStatement(string obj)
        {
            Object = obj;
        }

        public CCodeForeachStatement(string obj, params CCodeStatement[] statements)
            : this(obj)
        {
            AddRange(statements);
        }
        public string Object { get; set; }
        public override string ToString()
        {
            Clear();
            WriteLine("foreach (" + Object + ")");
            if (Count == 1)
            {
                _i++;
                Write(this[0].ToString());
                _i--;
            }
            else
            {
                BeginBlock();
                foreach (var s in this)
                    WriteLine(s.ToString());
                EndBlock();
            }
            return GetString();
        }
    }

    public class CCodeForStatement : CCodeStatement
    {
        public CCodeForStatement(string obj)
        {
            Object = obj;
        }

        public CCodeForStatement(string obj, params CCodeStatement[] statements)
            : this(obj)
        {
            AddRange(statements);
        }
        public string Object { get; set; }
        public override string ToString()
        {
            Clear();
            WriteLine("for (" + Object + ")");
            if (Count == 1)
            {
                _i++;
                foreach (var s in this)
                    WriteLine(s.ToString());
                _i++;
            }
            else
            {
                BeginBlock();
                foreach (var s in this)
                    WriteLine(s.ToString());
                EndBlock();
            }
            return GetString();
        }

    }

    #endregion

    #region statements

    public class CCodeLocal : CCodeField
    {
        public CCodeLocal(string type, string name)
            : base(type,name)
        {

        }
        private bool _declared;

        public override string ToString()
        {
            Modifier = Modifier.Private;
            if (!_declared)
                AsLocalReference = false;
            else
                AsLocalReference = true;
            _declared = true;
            return base.ToString();
        }
    }

    public class CCodeAssign : CCodeStatement
    {
        public CCodeStatement Left { get; set; }
        public CCodeStatement Right { get; set; }

        public CCodeAssign(CCodeStatement left, CCodeStatement right)
        {
            Right = right;
            Left = left;
        }
        public CCodeAssign(string left, string right)
        {
            Left = new CCodeSnippet(left);
            Right = new CCodeSnippet(right);
        }
        public override string ToString()
        {
            Clear();
            Left.Close = false;
            Right.Close = false;
            if (Left != null)
                Left.AsLocalReference = true;
            if (Right != null)
                Right.AsLocalReference = true;
            Write("{0}".F(Left.ToString()));
            if (Right!=null)
                Write(" = {0}".F(Right.ToString()));
            if (Close)
                Write(";");
            return GetString();
        }
    }

    public class CCodeNew : CCodeStatement
    {
        public List<CCodeStatement> Arguments { get; set; }
        public List<CCodeAssign> ObjectInitializers { get; set; }
        public string Type { get; set; }
        public CCodeNew(string type, params CCodeStatement[] args)
        {
            Arguments = new List<CCodeStatement>(args);
            ObjectInitializers = new List<CCodeAssign>();
            Type = type;
        }

        public override string ToString()
        {
            Clear();
            Write("new {0}".F(Type));
            if (!(Arguments.Count == 0 && ObjectInitializers.Count > 0))
                Write("(");
            foreach (var a in Arguments)
            {
                bool asp = a.AsLocalReference;
                a.AsLocalReference = false;
                Write(a.ToString());
                if (Array.IndexOf(Arguments.ToArray(), a) != Arguments.Count - 1)
                    Write(",");
                a.AsLocalReference = asp; // revert value back to original
            }
            if (!(Arguments.Count == 0 && ObjectInitializers.Count > 0))
                Write(")");
            foreach (var i in ObjectInitializers)
            {
                if (Array.IndexOf(ObjectInitializers.ToArray(), i) == 0)
                {
                    if (Close)
                        WriteLine();
                    else
                        Write(" ");
                    Write("{");
                    if (Close)
                    {
                        WriteLine();
                        _i++;
                    }
                }
                i.Close = false;
                Write(i.ToString());
                if (Array.IndexOf(ObjectInitializers.ToArray(), i) == ObjectInitializers.Count - 1)
                {
                    if (Close)
                    {
                        WriteLine();
                        _i--;
                    }
                    else
                        Write(" ");
                    Write("}");
                }
                else
                    Write(", ");
            }
            return GetString();
        }
    }

    public class CCodeMethodCall : CCodeStatement
    {
        public CCodeMethodCall(string methodName, params CCodeStatement[] args)
        {
            Arguments = new List<CCodeStatement>(args);
            MethodName = methodName;
        }

        public string MethodName { get; set; }
        public List<CCodeStatement> Arguments { get; set; }

        public override string ToString()
        {
            Clear();
            Write(MethodName + "(");
            foreach (var c in Arguments)
            {
                c.AsLocalReference = true;
                Write(c.ToString());
                if (Array.IndexOf(Arguments.ToArray(), c) != Arguments.Count - 1)
                    Write(", ");
            }
            Write(")");
            if (Close)
                Write(";");
            return GetString();
        }
    }

    public class CCodeAttribute : CCodeStatement
    {
        public CCodeAttribute(string attname)
        {
            AttributeName = attname;
        }

        public CCodeAttribute(string attname, string initializer) :this(attname)
        {
            Initializer = initializer;
        }
        public override string ToString()
        {
            Clear();
            Write("[" + AttributeName);
            if (!string.IsNullOrEmpty(Initializer))
                Write("(" + Initializer + ")");
            Write("]");
            return GetString();

        }
        public string AttributeName { get; set; }
        public string Initializer { get; set; }
    }
    
    public class CCodeSnippet : CCodeStatement
    {
        public string Code { get; set; }
        public CCodeSnippet(string code)
        {
            Code = code;
        }
        public override string ToString()
        {
            Clear();
            Write(Code);
            return GetString();
        }
    }

    
#endregion
}
